-
Notifications
You must be signed in to change notification settings - Fork 0
fix(server-actions): case-insensitive origin matching for CSRF protection #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: canary
Are you sure you want to change the base?
Conversation
…n for server action - use proxy to set a lowercase origin and a mixed-case x-forwarded-host to reproduce case-sensitive validation - use fixed origin and x-forwarded-host values so the test reproduces consistently across environments - include minimal app-dir fixtures (layout/page/action/form/const) - cover scenario where server action CSRF validation incorrectly depends on header casing
- trigger the server action via a button click - capture the server action response and assert the response status is 200 - get the submitted message from the action response and render it in the DOM - verify the DOM message matches the original payload to confirm the action executed successfully
…-insensitive - Clarifies that this test validates case-insensitive Origin vs Host matching
- Introduced ORIGIN_DOMAIN and X_FORWARDED_HOST constants to support case-insensitive checks in the ClientForm component. - Updated ClientForm to display current origin and X-Forwarded-Host values.
- Changed ORIGIN_DOMAIN to include 'https://' for accurate origin header setting. - Updated proxy function to use the modified ORIGIN_DOMAIN directly for consistency in origin handling.
- Eliminated console logging of 'origin', 'host', and 'x-forwarded-host' to streamline the proxy function and reduce unnecessary output.
- Replace example-domain.com with example.com (RFC 2606) to ensure the test domain never conflicts with a real website.
…der case - Describe missing origin header case (not host) in action handler.
…hosts for normalization
…Forwarded-Host headers - Convert Host and X-Forwarded-Host header values to lowercase for consistent comparison
…ility - Separate origin/host matching logic into a helper to make it easier to test - Keep explicit `!host` check in the conditional for readability even though the helper also guards
- Cover case-insensitive origin/host matching for Host and X-Forwarded-Host headers - Include a negative case where the host does not match the origin
- add case-insensitive matching for origin vs host - add case-insensitive matching for origin vs x-forwarded-host - organize existing mismatch case under a named test case
- allow host to be undefined to avoid type errors since it is already guarded upstream - keep defensive case-insensitive comparison in origin/host matching
…parison - Add e2e test to ensure that the `serverActions.allowedOrigins` config option matches origins case-insensitively. - Set the origin header to 'https://example.com' while configuring allowedOrigins as ['Example.COM'] to verify that different cases are treated as matching.
- Ensure that variations in case for both the origin and allowedOrigins are correctly recognized as matches.
- Normalize both the origin and allowedOrigins to lowercase for consistent comparison.
|
In Korean Version What?Server Actions의 CSRF Origin 검증을 대소문자 구분 없이(case-insensitive) 동작하도록 개선합니다.
Why?[RFC 3986 Section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2)에 따르면 URI의 host 컴포넌트는 대소문자를 구분하지 않습니다.
또한 [RFC 3986 Section 6.2.2.1](https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.2.1)에서도 동일하게 강조합니다.
([RFC 2119](https://datatracker.ietf.org/doc/html/rfc2119) 기준) RFC는 host를 소문자로 정규화하라고 “should”로 권고하지만, 이는 필수(must) 는 아닙니다. 즉, 업스트림(프록시)에서의 정규화를 신뢰할 수 없으므로, 비교를 수행하는 애플리케이션(Next.js)에서 대소문자 구분 없이 처리해야 합니다. Current behavior (현재 구현의 문제)현재 구현에서는 JavaScript 이 때문에 프록시가 서로 다른 casing으로 헤더를 전달하면(예: How?
Tests (테스트)Host/X-Forwarded-Host casing 불일치 e2e ( 실제 환경에서
이 테스트는 수정 전에는 대소문자 민감 비교로 인해 실패했으나, 수정 후에는 통과합니다.
설정값 매칭도 대소문자 구분 없이 처리됨을 검증합니다. 유닛 테스트
수정 전 재현 방법 (before the fix)아래 커밋으로 체크아웃한 뒤, 해당 e2e 테스트를 실행하면 기존 동작(대소문자 구분 매칭) 을 확인할 수 있습니다. 1) Origin vs Host / X-Forwarded-Host case mismatch (e2b569a) ✅ 재현 (video + logs)2026-01-24.5.32.06.mov2) ✅ 재현 (video + logs)2026-01-24.5.36.35.mov |
… server action config
What?
Make Server Actions CSRF origin validation case-insensitive for both:
Host/X-Forwarded-Hostheader comparison withOriginserverActions.allowedOriginsconfig matchingWhy?
Per RFC 3986 Section 3.2.2 states the host component of URIs is case-insensitive:
Per RFC 3986 Section 6.2.2.1 reinforces:
(per RFC 2119) While the RFC recommends ("should") that producers normalize to lowercase, this is not mandatory. In practice, proxies may preserve the original case of
Host/X-Forwarded-Hostheaders. Since upstream normalization is not guaranteed, the application(Next.js) performing the comparison must handle case-insensitive matching.Current behavior in the existing code
In the current implementation, normalization via the JavaScript
URLAPI is applied only to theOriginheader value. Meanwhile,Host/X-Forwarded-Hostheaders retain their original casing, which can lead to false-positive CSRF blocks when proxies forward headers with different casing (e.g.,Example.comvsexample.com).How?
HostandX-Forwarded-Hostto lowercase inparseHostHeader()isCsrfOriginAllowed()Tests
1. E2E test for Host/X-Forwarded-Host case mismatch (
host-origin-case-insensitive)To reproduce the real-world scenario where
OriginandX-Forwarded-Hosthave different casing, added a proxy-based e2e test. The proxy sets:Origin: https://example.com(lowercase, as normalized by URL API)X-Forwarded-Host: Example.com(mixed case, simulating proxy behavior)This test failed before the fix due to case-sensitive comparison, now passes.
2. E2E test for
allowedOriginsconfig case mismatch (config-match-case-insensitive)Added an e2e test where
next.config.jsspecifiesserverActions.allowedOrigins: ['Example.COM']while the actual origin header ishttps://example.com. This verifies that the config matching is also case-insensitive.3. Unit tests
action-handler.test.ts: Tests forparseHostHeader()lowercasing andisOriginMatchingHost()case-insensitive comparisoncsrf-protection.test.ts: Tests forisCsrfOriginAllowed()case-insensitive matching between origin and allowedOrigins patternsTo reproduce (before the fix)
You can confirm the previous behavior (case-sensitive matching) by checking out the commits below and running the corresponding e2e tests.
1) Origin vs Host / X-Forwarded-Host case mismatch (e2b569a)
✅ Reproduction (video + logs)
2026-01-24.5.32.06.mov
2)
serverActions.allowedOriginsconfig case mismatch (a2d6510)✅ Reproduction (video + logs)
2026-01-24.5.36.35.mov
Fixes #issue